#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/io.h>
#include <linux/string.h>

#define DEVICE_NAME "led_drv"

/*addr:0x02290000+0x14*/
static volatile unsigned int *GPIO5_3;
/*addr:0x020AC004*/
static volatile unsigned int *GPIO5_3DIR;
/*addr:0x020AC000*/
static volatile unsigned int *GPIO5_3DR;

static int major;
static struct class *led_class;


ssize_t led_read(struct file *fd, char __user *buf, 
    size_t size, loff_t *lof){
        return 0;
}

ssize_t led_write(struct file *fd, const char __user *buf, 
    size_t size, loff_t *lof){
    char cmd[4];  // Store string command (e.g. "on\0")
    int err;

    err = copy_from_user(cmd, buf, size);
    if(err){
        pr_err("Failed to copy data from user space (error: %d)\n", err);
        return -EFAULT;
    }
    
    cmd[size] = '\0';  // Ensure string termination
    printk(KERN_INFO "command from userspace: '%s'\n", cmd);
    if (strncmp(cmd, "on", 2) == 0) {
        *GPIO5_3DR &= ~(1<<3);  // LED on
        printk(KERN_INFO "led on"); 
    }
    
    if (strncmp(cmd, "off", 3) == 0) {
        *GPIO5_3DR |= (1<<3);   // LED off
        printk(KERN_INFO "led off");   
    } 

    return size;  // Return number of bytes processed
}

int led_open(struct inode *node, struct file *fd){
    /*enable GPIO5 as GPIO mode*/
    //set the lower 4 bits of GPIO5_3 to 0
    *GPIO5_3 &= ~0xf;/*~0xf-->0b111...11110000*/
    //set the lower 4 bit of GPIO5_3 as 0101 
    *GPIO5_3 |= 0x5;

    /*config GPIO5_3 as output mode*/
    //set the GPIO5_3 bit3 to 1
    *GPIO5_3DIR |= (1<<3);

    printk(KERN_INFO "led driver initialized\n");
    return 0;
}

int led_close(struct inode *node, struct file *fd){
    return 0;
}

static struct file_operations led_fop = {
    .owner   = THIS_MODULE,
	.read    = led_read,
	.write   = led_write,
    .open    = led_open,
    .release = led_close,
};

static int __init led_init(void) {
    int ret = 0;

    major = register_chrdev(0, DEVICE_NAME, &led_fop);
    if (major < 0) {
        ret = major;
        printk(KERN_ERR "led_driver: Failed to register char device, error %d\n", ret);
        goto err_register_chrdev;
    }
    
    /*addr:0x02290000+0x14*/
    GPIO5_3 = ioremap(0x02290000 + 0x14, 4);
    /*addr:0x020AC004*/
    GPIO5_3DIR = ioremap(0x020AC004, 4);
    /*addr:0x020AC000*/
    GPIO5_3DR = ioremap(0x020AC000, 4);


    led_class = class_create(THIS_MODULE, DEVICE_NAME);
    if (IS_ERR(led_class)) {
        ret = PTR_ERR(led_class);
        printk(KERN_ERR "led_driver: Failed to create class, error %d\n", ret);
        goto err_class_create;
    }
    
    if (!device_create(led_class, NULL, MKDEV(major, 0), NULL, DEVICE_NAME)) {
        ret = -ENODEV;
        printk(KERN_ERR "led_driver: Failed to create device\n");
        goto err_device_create;
    }

    printk(KERN_INFO "led_driver: sucessed to create device\n");
    return 0;

    err_device_create:
        class_destroy(led_class);

    err_class_create:
        unregister_chrdev(major, DEVICE_NAME);

    err_register_chrdev:
        return ret;
}

static void __exit led_exit(void){
    iounmap(GPIO5_3);
    iounmap(GPIO5_3DIR);
    iounmap(GPIO5_3DR);

    device_destroy(led_class, MKDEV(major, 0));
    class_destroy(led_class);
    unregister_chrdev(major, DEVICE_NAME);

    printk(KERN_INFO "device %s(%d) unregisted\n", DEVICE_NAME, major);
}

module_init(led_init);
module_exit(led_exit);

MODULE_AUTHOR("fison");
MODULE_DESCRIPTION("hello");
MODULE_LICENSE("GPL");